EmailSender Service using FluentEmail & Razor Templates

Sending emails is a very important functionality for any website nowdays, either to send account verification emails, newsletter emails or even notification emails.
Today I’ll be showing how you can create your own EmailSender Service in ASP.NET Core using FluentEmail and Razor templates for rich HTML emails.

EmailSender interface

If you’re working with a project that implements a Clean Architecture, you’ll want to create an interface in your Core project to define what you need the service to do, as well as to have the possibility to use it from the Core project too.

public interface IEmailSender
{

    Task<bool> SendUsingTemplate(string to, string subject, EmailTemplate template, object model);

}

The method SendUsingTemplate has the following signature:

  • Task<bool>, the method is async and will return true if the email was successfully sent or false otherwise.
  • string to, the recipient email address.
  • string subject, the email’s subject/title.
  • EmailTemplate template, which template to use (enum).
  • object model, since most of the time you’ll want to send more information in the email, you can pass a model (or an anonymous object) to the template.

Note: the method doesn’t require a from email address, that’s because we’ll configure it in our Startup class since we usually only use one.

public enum EmailTemplate
{
    EmailConfirmation,
    ChangeEmail
}

I use an enum for the template names to remove the chance of someone getting the template’s name wrong. Magic strings are always a bad idea.

EmailSender Implementation

Prerequisites

For the implementation of the service, you’ll only need the FluentEmail.Core NuGet package: Install-Package FluentEmail.Core

Implementation

public class EmailSender : IEmailSender
{

    private const string TemplatePath = "Web.Api.Infrastructure.Services.Emails.Templates.{0}.cshtml";
    private readonly IFluentEmail _email;
    private readonly ILogger<EmailSender> _logger;

    public EmailSender(IFluentEmail email, ILogger<EmailSender> logger)
    {
        _email = email;
        _logger = logger;
    }

    public async Task<bool> SendUsingTemplate(string to, string subject, EmailTemplate template, object model)
    {
        var result = await _email.To(to)
            .Subject(subject)
            .UsingTemplateFromEmbedded(string.Format(TemplatePath, template), ToExpando(model), GetType().Assembly)
            .SendAsync();

        if (!result.Successful)
        {
            _logger.LogError("Failed to send an email.\n{Errors}", string.Join(Environment.NewLine, result.ErrorMessages));
        }

        return result.Successful;
    }

}

The code is pretty straight-forward, we use FluentEmail’s IFluentEmail object (that gets injected) to send the email asynchronously.
I often prefer to log the errors in the service and only return a boolean response. I don’t think the client needs to know why the email wasn’t sent, the developpers need to deal with that.

The only thing you’ll need to change is the TemplatePath constant, which needs to contain the full namespace name where your template files (.cshtml files) reside.

In order to be able to use anonymous objects in the templates, we’ll need to convert them into Expando objects. This is a “limitation” that RazorLight (the library that FluentEmail uses to handle Razor Templates) has.

private static ExpandoObject ToExpando(object model)
{
    if (model is ExpandoObject exp)
    {
        return exp;
    }

    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var propertyDescriptor in model.GetType().GetTypeInfo().GetProperties())
    {
        var obj = propertyDescriptor.GetValue(model);

        if (obj != null && IsAnonymousType(obj.GetType()))
        {
            obj = ToExpando(obj);
        }

        expando.Add(propertyDescriptor.Name, obj);
    }

    return (ExpandoObject)expando;
}

private static bool IsAnonymousType(Type type)
{
    bool hasCompilerGeneratedAttribute = type.GetTypeInfo()
        .GetCustomAttributes(typeof(CompilerGeneratedAttribute), false)
        .Any();

    bool nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
    bool isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;

    return isAnonymousType;
}

The code might look alien but all it does is convert every anonymous object to an Expando object recursively (since anonymous objects can have anonymous objects…).

Razor Templates

Based on your TemplatePath value, you will need to put all your cshtml template files in that folder and they must be marked as: Embedded resource.

For example, this my ChangeEmail template file:

Properties of an email template file

Properties of an email template file

Configuration

Prerequisites

When configuring the EmailSender service, you’ll need the following NuGet packages:

  • FluentEmail.Smtp
  • FluentEmail.Razor

Services

// Configure IFluentEmail
services.AddFluentEmail(Configuration["email_address"])
.AddRazorRenderer()
.AddSmtpSender(new SmtpClient("smtp.gmail.com", 587)
{
    Credentials = new NetworkCredential(Configuration["email_address"], Configuration["email_password"]),
    EnableSsl = true
});

// Add our service
services.TryAddScoped<IEmailSender, EmailSender>();

In this example I’m using a GMAIL account to send the emails, you can use whatever email server you want, even your own.

Conclusion

As you can see, creating an EmailSender service is pretty easy and straight-forward and can be used in all your projects. Having the ability to send rich HTML emails is a must for every website nowdays.

Zanid Haytam Written by:

Zanid Haytam is an enthusiastic programmer that enjoys coding, reading code, hunting bugs and writing blog posts.

comments powered by Disqus